#include <common.h>
#include <gpio.h>
#include <cpm.h>
#include <sfc.h>

#define sfc_readl(o)		readl(SFC_BASE + (o))
#define sfc_writel(b ,o)	writel(b, SFC_BASE + (o))

struct sfc_config {
	char index;
	char flash_type;
};

struct sfc_config  sfc = {
	.index = CONFIG_SFC_INDEX,
	.flash_type = CONFIG_SFC_TYPE,
};

void sfc_hardware_init()
{
	unsigned int tmp = 0;

	tmp = (THRESHOLD << THRESHOLD_OFFSET) | WP_EN | BURST_MD_MSK | (0x1 << PHASE_NUM_OFFSET);
	sfc_writel(tmp, SFC_GLB0);

	tmp = (CEDL | HOLDDL | WPDL);
	sfc_writel(tmp, SFC_DEV_CONF);

	sfc_writel((END_BIT | TREQ_BIT | RREQ_BIT | OVER_BIT | UNDER_BIT), SFC_INTC);

	sfc_writel((END_BIT | TREQ_BIT | RREQ_BIT | OVER_BIT | UNDER_BIT), SFC_SCR);

	sfc_writel(0, SFC_CGE);
}

void sfc_init()
{

	unsigned int index = sfc.index;

	switch(index) {
	case 0:
		__gpio_a_as_sfc0_boot();
		__cpm_start_sfc0();
		break;
	case 1:
		__gpio_a_as_sfc1_boot();
		__cpm_start_sfc1();
		break;
	default:
		__gpio_a_as_sfc0_boot();
		__cpm_start_sfc0();
		break;
	}

	sfc_hardware_init();
}


static int sfc_read_data(unsigned int *dst, unsigned int len)
{
	unsigned int value;
	unsigned int tmp_len = 0;
	unsigned int fifo_num = 0;
	int i;

	while (1) {
		value = sfc_readl(SFC_SR);
		if (value & RECE_REQ) {
			sfc_writel(RREQ_BIT, SFC_SCR);
			if((len - tmp_len) > THRESHOLD) {
				fifo_num = THRESHOLD;
			} else {
				fifo_num = len - tmp_len;
			}

			for(i = 0; i < fifo_num; i++) {
				*dst++ = sfc_readl(SFC_RM_DR);
				tmp_len++;
			}
		} else {
			mdelay(1);
		}

		if (value & OVER)
			serial_puts("error! sfc fifo over-run\n");
		if (value & UNDER)
			serial_puts("error! sfc fifo under-run\n");

		if(len == tmp_len)
			break;
	}

	sfc_writel((END_BIT | TREQ_BIT | RREQ_BIT | OVER_BIT | UNDER_BIT), SFC_SCR);

	return 0;
}

static void sfc_set_read_reg(unsigned int cmd, unsigned int addr,
			unsigned int addr_plus, unsigned int addr_len, unsigned int data_en)
{
	unsigned int tmp;

	sfc_writel((END_BIT | TREQ_BIT | RREQ_BIT | OVER_BIT | UNDER_BIT), SFC_SCR);

	if (data_en) {
		tmp = (addr_len << ADDR_WIDTH_OFFSET) | CMDEN |
			DATEEN | (cmd << CMD_OFFSET);
	} else {
		tmp = (addr_len << ADDR_WIDTH_OFFSET) | CMDEN |
			(cmd << CMD_OFFSET);
	}
	sfc_writel(tmp, SFC_TRAN_CONF0(0));

	sfc_writel(addr, SFC_DEV_ADDR(0));
	sfc_writel(addr_plus, SFC_DEV_ADDR_PLUS(0));
	sfc_writel(START, SFC_TRIG);
}

static int sfc_read(unsigned int addr, unsigned int addr_plus,
	unsigned int addr_len, unsigned int *data, unsigned int len)
{
	int ret = 0;

	sfc_writel(len, SFC_TRAN_LEN);

	sfc_set_read_reg(0x03, addr, addr_plus, addr_len, 1);

	ret = sfc_read_data(data, (len + 3) / 4);

	return ret;
}

int sfc_block_read(unsigned int start_block, unsigned int block_cnt, void *buf)
{
	int ret = 0;
	char *block_buf = (char *)buf;

	for(int i = 0; i < block_cnt; i ++) {
		ret = sfc_read(start_block + (i * SFC_BLOCK_SIZE), 0, 3,
				(unsigned int *)(block_buf + (i * SFC_BLOCK_SIZE)), SFC_BLOCK_SIZE);
		if(ret != 0)
			return -1;
	}

	return 0;
}
